﻿/*
 * GridMath
 * 
 * initial implementation : 31.8.2007
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.Geometry.GridSupport;
using Petra.Gis.ReferenceSystemServices;
//using Petra.Gis.ReferenceSystemServices.Transformations;


namespace Petra.Gis.Geometry.Utils
{

  /// <summary>
  /// provides Grid specific mathematic functions and computations
  /// </summary>
  public static class GridMath
  {


    //public static GridBlock[] SelectGridBlocksByRange(Grid g, int FrameId, BoundingBox selectBounds)
    //{
    //  GridBlock[] result;


    //}




    //public static GridBlock[,] SelectGridCellsByMapRange(Grid g, int FrameId, BoundingBox mapSrsWorldBounds)
    //{

    //  //transform map srs bounds to grid srs
    //  BoundingBox outGridBounds = g.SRSTransform.TransformBox(mapSrsWorldBounds);


    //  GridBlock[] result;

    //  foreach(GridBlock b in g.Data.Frames[FrameId].Blocks)
    //    if(b.Bounds.Intersects(outGridBounds))
          


    //}




    public static GridFrame SelectGridFrameByPixelSize(Grid g, double metersPerCell, ProjectedCoordinateSystem dataSRS)
    {

//      if (mapCellWidthDef.Count != 2)
//        throw new ArgumentException(@"invalid cell size definition, must be Coordinate list with 2 point
//                                      whiches horizontal distance defines requested grid cell size");

      double currentFrameWCW; //current frame world cell width
      double _metersPerSrsStorageUnit;

      // get meter size of each frame cell
      // if map projection storage units is angular, then compute
      // radians per meter from defined ellipsoid
      //ProjectedCoordinateSystem dataSRS = (ProjectedCoordinateSystem)forwardTransform.Source;

      if (dataSRS.Unit is AngularUnit)
      {
        _metersPerSrsStorageUnit = Math.Sin(((AngularUnit)dataSRS.Unit).RadiansPerUnit) * (
              dataSRS.GeographicCoordinateSystem.Datum.Ellipsoid.SemiMajorAxis *
              dataSRS.GeographicCoordinateSystem.Datum.Ellipsoid.AxisUnit.MetersPerUnit);
      }
      else
        _metersPerSrsStorageUnit = ((DistanceUnit)dataSRS.Unit).MetersPerUnit;

      ////transform cellWidthDef (horizontal pixel size) to grid coordinate units
      //g.SRSTransform.Transform(mapCellWidthDef);
      //double pixelSize = Math.Abs(mapCellWidthDef[1].X - mapCellWidthDef[0].X);

      //search for Frame
      //if only one frame, dont search ;-)
      if(g.Data.Frames.Count == 1)
        return g.Data.Frames[0];

      GridFrame lastFrame = g.Data.Frames[0];
      foreach (GridFrame f in g.Data.Frames)
      {

        currentFrameWCW = (g.BoundingBox.Width * _metersPerSrsStorageUnit) / f.Width;
        if (currentFrameWCW > metersPerCell)
          break;
        else
          lastFrame = f;
      }

      return lastFrame;
    }





    //public static GridFrame SelectSubframeByRange(GridFrame srcFrame, BoundingBox mapSrsWorldBounds)
    //{
    //  throw new NotImplementedException();
    //}




    //public static GridFrame TransformFrame(GridFrame frame, SRSTransform activeTransform)
    //{
    //  // todo: implement transform for different coordinate systems

    //  frame.Bounds = activeTransform.TransformBox(frame.Bounds);

    //  return frame;
    //}




    //public static GridCells TransformCells(GridCells cells, SRSTransform activeTransform)
    //{
    //  // todo: implement transform for different coordinate systems

    //  cells.Bounds = activeTransform.TransformBox(cells.Bounds);

    //  return cells;
    //}






    public static float GetScaleByVectorAndSize(Vector v, int size)
    {
      double vectorSize2d = Math.Sqrt((v.I * v.I) + (v.J * v.J));
      return (float)(vectorSize2d / (size * 1.0));
    }





    /// <summary>
    /// returns value of rotation angle from
    /// Value is in cw direction
    /// </summary>
    /// <param name="v">vector</param>
    /// <returns>angle starting from horizontal right positive direction x axis</returns>
    public static float GetVectorCcwAngle(Vector v)
    {

      //// prepare sum of input vectors
      //Vector tmp = new Vector(
      //  v.I + v.I,
      //  v.J + v.J,
      //  0);

      // originally this sum was 45 deg  cw
      // now compute its real angle ;-)
      double result;

      // validate first
      if (v.I == 0 && v.J == 0)
        return 0;

      // if vector is vertical
      if (v.I == 0)
        if (v.J > 0)
          result = 270;
        else
          result = 90;
      // else if is horizontal
      else if (v.J == 0)
        if (v.I > 0)
          result = 0;
        else
          result = 180;
      // else common position, use tangent
      else
        result = getAngle(v);

      return (float)result;


      //double result1, result2;

      //// validate first
      //if (horizontal.I == 0 && horizontal.J == 0 && vertical.I == 0 && vertical.J == 0)
      //  return 0;

      //// resolve horizontal vector first
      //// if horizontal vector is vertical
      //if (horizontal.I == 0)
      //  if (horizontal.J > 0)
      //    result1 = 270;
      //  else
      //    result1 = 90;
      //// else if is horizontal
      //else if (horizontal.J == 0)
      //  if (horizontal.I > 0)
      //    result1 = 0;
      //  else
      //    result1 = 180;
      //// else common position, use tangent
      //else
      //  result1 = getAngle(horizontal);


      //// resolve vertical vector now
      //// if vertical vector is vertical
      //if (vertical.I == 0)
      //  if (vertical.J > 0)
      //    result2 = 180;
      //  else
      //    result2 = 0;
      //// else if is horizontal
      //else if (vertical.J == 0)
      //  if (vertical.I > 0)
      //    result2 = 270;
      //  else
      //    result2 = 90;
      //// else common position, use tangent
      //else
      //  result2 = getAngle(vertical);

      //// return angle bisect both input values of angle...
      //result2 -= 90;
      //if (result2 < 0)
      //  result2 = 360 + result2;

      //double tmp = (result2 - result1) / 2;

      //double result = result1 + tmp;

      //if (result >= 360)
      //  result = result - 360;

      //return (float)result;

    }






    // map of quadrants
    //
    //       ^ y+
    //       |
    //   2   |   1
    //       |        x+
    // ------+------- >
    //       |
    //   3   |   4
    //       |

    // returns angle in degrees
    // in ccw direction
    private static double getAngle(Vector v)
    {
      double result = Math.Atan(v.J / v.I) * 180 / Math.PI;

      if (v.I > 0)
      {
        if (v.J > 0)
          result = 360 - result; // 1st. quadrant
        else
          return -result; // 4th. quadrant, no change
      }
      else
        result = 180 - result;
        //if (v.J > 0)
        //  result = 180 - result; // 2st. quadrant
        //else
        //  result = 180 - result; // 3th. quadrant

      return result;

    }

  }

}
